/** A controller is a Mobile's AI implementation and is responsible for making
 * desicions for the mobile and carrying out those decisions.
 *
 * The controller system defines these stages in the decision making process for
 * mobiles:
 * target		Acquire a target for combat
 * combat		Engage the target in combat
 *
 * The controller system for dhack identifies these methods used in the
 * mobile decision making stages:
 * sense		How does the mobile sense targets, and what targets does it look for
 * alert		How does the mobile behave when it senses a target
 * idle			How the mobile behaves when it has nothing to do
 * wimpy		How the mobile decides to flee
 * flee			How the mobile flees a danger or threat
 * attack		How the mobile attacks its target
 * follow		How does the mobile follow a target
 *
 * A controller consists of a set of method definitions that are used in the
 * decision making process. These methods are defined within pools below. These
 * decision making methods can be mixed and matched to create complex AIs.
 *
 * @param mobile The mobile the controller is to control.
 * @param desc The controller description, an object containing keys matching the methods of the decision making process with values that identify what methods to use.
 */
function Controller(mobile, desc)
{
	this.mobile = mobile;
	this.desc = desc;
	this.stage = "target";
	this.isFleeing = false;
	this.target = null;
	this._bindMethods();
}
/** A sense method accepts no parameters and returns a target mobile or null if
 * no suitable target was found.
 */
Controller._sense =
{
	none: function() { return null; },
	lookForPlayer: function()
	{
		if(this.mobile.parent.isVisibleFrom(this.mobile.x, this.mobile.y, dhack.player.x, dhack.player.y, this.mobile.visibilityRange))
			return dhack.player;
	}
}
/** An alert method is called after this.target is set.
 */
Controller._alert =
{
	none: function() { return; },
	attack: function()
	{
		this.stage = "combat";
	},
	run: function()
	{
		this.stage = "combat";
		this.isFleeing = true;
	}
}
/** An idle method is called whenever there is nothing else to do. There are no
 * preconditions or expectations.
 */
Controller._idle =
{
	none: function()
	{
		this.mobile.tryMove(0, 0);
		return;
	},
	wander: function()
	{
		for(var i = 0; i < 5; ++i)
		{
			if(this.dir === undefined ||
				Math.random() > 0.85)
				this.dir = Math.floor(Math.random() * 4);
			var dx = 0;
			var dy = 0;
			switch(this.dir)
			{
				case 0: dx = 1; break;
				case 1: dx = -1; break;
				case 2: dy = 1; break;
				case 3:
				default: dy = -1; break;
			}
			if(this.mobile.tryMove(dx, dy))
				break;
		}
	}
}
/** A wimpy method is called in combat to determine if the mobile should start
 * running. It returns true if the mobile should start running, false otherwise.
 * this.target will be set.
 */
Controller._wimpy =
{
	none: function() { return false; },
	run: function()
	{
		return true;
	}
}
/** A flee method is called after this.target is set and is used to run away
 * from that target.
 */
Controller._flee =
{
	none: function()
	{
		this.mobile.tryMove(0, 0);
		return;
	},
	run: function()
	{
		var dx = this.mobile.x - this.target.x;
		var dy = this.mobile.y - this.target.y;
		dx = dx < 0 ? -1 : dx > 0 ? 1 : Math.random() > 0.5 ? -1 : 1;
		dy = dy < 0 ? -1 : dy > 0 ? 1 : Math.random() > 0.5 ? -1 : 1;
		if(Math.random() > 0.5)
		{
			if(this.mobile.tryMove(dx, 0))
				return;
		}
		if(this.mobile.tryMove(0, dy))
			return;
		if(this.mobile.tryMove(dx, 0))
			return;
	}
}
/** An attack method is called after this.target is set and is used to attack
 * that target. Returns true if the target was valid for the attack, false if
 * we need to move closer.
 */
Controller._attack =
{
	none: function() { return false; },
	attack: function()
	{
		return this.mobile.attack(this.target);
	}
}
/** A follow method is called after this.target is set and is used to follow
 * that target.
 */
Controller._follow =
{
	none: function()
	{
		this.mobile.tryMove(0, 0);
		return;
	},
	follow: function()
	{
		var dx = this.mobile.x - this.target.x;
		var dy = this.mobile.y - this.target.y;
		dx = dx < 0 ? 1 : dx > 0 ? -1 : 0;
		dy = dy < 0 ? 1 : dy > 0 ? -1 : 0;
		if(dx != 0 &&
			Math.random() > 0.5)
		{
			if(this.mobile.tryMove(dx, 0))
				return;
		}
		if(dy != 0 &&
			this.mobile.tryMove(0, dy))
			return;
		if(dx != 0 &&
			this.mobile.tryMove(dx, 0))
			return;

		// If we get here we have not moved, try an off-center move maybe
		if(dx == 0 &&
			this.mobile.tryMove((Math.random() > 0.5 ? -1 : 1), 0))
			return;
		if(dy == 0 &&
			this.mobile.tryMove(0, Math.random() > 0.5 ? -1 : 1))
			return;
	}
}
Controller.prototype =
{
	/** Called after deserialization.
	 */
	fromRON: function()
	{
		this._bindMethods();
	},
	/** Bind all of the methods mentioned in this.desc to our method handles.
	 */
	_bindMethods: function()
	{
		if(!this.desc)
			return;
		this.sense = Controller._sense[this.desc.sense];
		this.alert = Controller._alert[this.desc.alert];
		this.idle = Controller._idle[this.desc.idle];
		this.wimpy = Controller._wimpy[this.desc.wimpy];
		this.flee = Controller._flee[this.desc.flee];
		this.attack = Controller._attack[this.desc.attack];
		this.follow = Controller._follow[this.desc.follow];
	},
	/** Execute a turn on behalf of our mobile. */
	doTurn: function()
	{
		// First we validate that our target is still valid
		if(this.target &&
			!this.target.parent)
		{
			this.target = null;
			this.stage = "target";
			this.isFleeing = false;
		}
		switch(this.stage)
		{
			case "target":
				this.target = this.sense();
				if(this.target)
					this.alert();
				else
					this.idle();
				break;
			case "combat":
				if(!this.isFleeing)
					this.isFleeing = this.wimpy();
				if(this.isFleeing)
					this.flee();
				else
				{
					if(!this.attack())
						this.follow();
				}
				break;
		}
	},
	/** Called when the controlled mobile is hit. */
	wasHitBy: function(source)
	{
		this.stage = "combat";
		this.target = source;
	}
}
ron.registerCtor(Controller, "Controller");
